A tracing framework is a collection of software routines and tools that permit a user to instrument and record the activity of one or more executing programs, including an operating system kernel. Tracing frameworks typically permit users to describe instrumentation requests by naming one or more probes, which are locations of interest that can be used as data-recording sites within an instrumented program. Tracing frameworks also permit users to associate these probes with one or more actions. The actions describe what tracing operations should be performed when the executing instrumented program passes through the probe site (i.e., when a probe triggers). Tracing frameworks typically provide either a defined set of actions at each probe, a set of user-selectable actions, or the ability to execute an essentially arbitrary set of actions composed in a programming language (such as C, C++, or Pascal).
In tracing frameworks that support a programming language for describing actions, tracing operations (in the form of language statements) are compiled into an intermediate form or directly into machine code. The tracing operations are executed when a probe triggers. In tracing frameworks that compile probe descriptions and actions from a programming language into an intermediate form, the compiler is typically implemented as a user application that then communicates the results of the compilation to the operating system kernel where the probe instrumentation is performed. Communication is typically performed using one or more system calls or device driver calls combined with a set of data structures that together form a binary interface between the trace program compiler and the tracing framework instrumentation service.
The structure of tracing programs can be somewhat complex; likewise the associated data structures are often complex and bind the implementation artifacts of the trace program compiler tightly with the operating system service. A traditional mechanism for separating compiler implementation artifacts for encoding an executable program from the executing operating system service is to define an object file format. Traditional programming language compilers for all operating systems typically encode their compiled output object code in a file format that is well-documented and able to be processed by the operating system.
Two common such object file formats supported by modern operating systems are Executable and Linkable Format (ELF) and Common Object File Format (COFF). The design of these file formats is centered around the encoding of traditional user program concepts, such as (1) a single, well-defined entry point for the program (e.g., in C and C++, the routine named “main” or an operating system wrapper around it named “_start”); and (2) a single set of sections associated with the program (e.g., the program object code and the program data) that are to be loaded into memory by the operating system before executing the program.
A tracing program is unlike a traditional program written in a programming language because it has no notion of a single entry point and linear or threaded flow of control. Instead, a tracing program acts as a symbiotic entity acting in response to the control flow of another program: the instrumented program that is being traced. Each tracing program probe description is effectively a potential entry point into the trace program, and multiple entry points can occur in parallel if the traced program itself is multi-threaded. As a result of these differences, existing tracing frameworks that support persistent storage of compiled tracing programs have not used object file formats and have instead chosen to encode tracing programs using various arrangements of the in-memory data structures used to communicate the tracing program to the instrumentation service.